package com.baifc.config;

import com.baifc.datasource.SpitterRepository;
import com.baifc.service.SpitterUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.crypto.password.StandardPasswordEncoder;

import javax.sql.DataSource;

/**
 * projectName: spring-in-action-web
 * packageName: com.baifc.config
 * Created: 2019/5/21.
 * Auther: baifc
 * Description: spring Security java配置方案，使用@EnableWebMvcSecurity将会启用web安全功能，必须配置在实现了WebSecurityConfigurer的bean中
 * 或者扩展了WebSecurityConfigurerAdapter，该类是一个最简单的配置方式
 */
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    /**
     * 配置用户存储，基于内存
     * @param auth
     * @throws Exception
     */
//    @Override
//    public void configure(AuthenticationManagerBuilder auth) throws Exception {
//        // 配置使用内存的用户存储
//        auth.inMemoryAuthentication()   // 通过inMemoryAuthentication()方法，我们可以启用、配置并任意填充基于内存的用户存储
//                // roles方法给定用户授予一个或多个角色权限，roles方法是authorities()方法的简写，会给值添加一个"ROLE_"的前缀
//                // roles("USER")方法和authorities("ROLE_USER")是等价的
//                .withUser("user").password("password").roles("USER")
//                .and()
//                .withUser("admin").password("password").roles("USER", "ADMIN");
//
//    }


//    @Autowired
//    private DataSource dataSource;
//
//    /**
//     * 配置用户存储，基于关系型数据库
//     * @param auth
//     * @throws Exception
//     */
//    @Override
//    public void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.jdbcAuthentication()
//                .dataSource(dataSource)
//                // 获取用户名、密码以及是否启用的信息，来进行用户认证
//                .usersByUsernameQuery("select username, password, true from Spitter where username=?")
//                // 查询了用户所授予的权限，用来鉴权
//                .authoritiesByUsernameQuery("select username, 'ROLE_USER' from Spitter where username = ?")
//                // 指定了一个密码转码器
//                .passwordEncoder(new StandardPasswordEncoder("abcd"));
//                // 查找用户作为组群的成员进行所授予的权限
////                .groupAuthoritiesByUsername("sql");
//    }

    @Autowired
    private SpitterRepository spitterRepository;

    /**
     * 根据自定义userDetailsService接口实现来配置用户存储
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(new SpitterUserService(spitterRepository));
    }

    /**
     * 请求拦截配置
     * @param httpSecurity
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 启用spring提供的默认登录页
                .formLogin()
                // 添加自定义的登录页
                .loginPage("/login")
                .and()
                // 启用用户记忆功能
                .rememberMe()
                // 在cookie中存储的token最长时间是多少
                .tokenValiditySeconds(2419200)
                // 设置存在于cookie中的token的私钥
                .key("spitterKey")
                .and()
                // 该方法返回的对象，配置请求级别的安全性细节
                .authorizeRequests()
                // 指定了路径，需要对"/spitters/me"的请求进行认证，该方法还支持通配符，例如"/spitters/**"
                // 也可以用regexMatchers("/spitters/**")，这是正则风格的
                .antMatchers("/spitters/me")
                // 指用户不仅需要认证，还需要"ROLE_SPITTER"权限。
                // 代替方案，使用hasRole("SPITTER")是等价的
                .hasAnyAuthority("ROLE_SPITTER")
                // 要求在执行该请求时，必须已经登录了应用
//                .authenticated()
                // 对该请求的POST请求进行认证
                .antMatchers(HttpMethod.GET, "/home").authenticated()
                .antMatchers("/spitter/order")
                // 这个方法支持spEL表达式，结果返回为true，才能允许访问
//                .access("hasRole('ROLE_SPITTER')")
                // hasIpAddress表示请求来自指定IP，才能允许访问
                .access("hasRole('ROLE_SPITTER') and hasIpAddress('localhost')")
                // 所有请求
                .anyRequest()
                // 允许请求没有任何安全限制
                .permitAll()
                .and()
                // 这个方法可以为各种URL模式生命要求的通道
                .requiresChannel()
                // 表示只要是对/spitter/form的请求，都将被视为安全通道，并自动将请求重定向到https上
                .antMatchers("/spitter/form").requiresSecure()
                // 表示就算已https放松了对"/"的请求，也将会被重定向到不安全的http通道（有些请求并需需要通过https传送）
                .antMatchers("/").requiresInsecure();

        /*
         在这个方法中，可以将任意数量的antMatchers、regexMatchers、anyRequest链接起来
         这些规则会按照给定顺序发挥作用，这里一定要将最具体的路径放在前面，最不具体的，比如anyRequest放在最后面
         不然不具体的路径配置会覆盖掉具体的路径配置
          */

    }
}
